package prova;

import static javassist.ClassPool.getDefault;
import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

import org.apache.myfaces.test.runners.TestPerClassLoaderRunner;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.google.code.synchunit.MonitoredThread;
import com.google.code.synchunit.SynchUnit;

@RunWith(TestPerClassLoaderRunner.class)
public class PP {

	@Test
	public void doSalcazzos() throws NotFoundException, CannotCompileException,
			ClassNotFoundException {
		ClassLoader parentClassLoader = Thread.currentThread()
				.getContextClassLoader();
		ClassLoader cl = new MyClassLoader1(parentClassLoader);
		cl.loadClass("prova.MyClass");

		MyClass k = new MyClass();

		assertEquals(0, k.getCurrentValue());

		k.increment();

		assertEquals(0, k.getCurrentValue());
	}

	@Test
	public void doSalcazzos2() throws NotFoundException,
			CannotCompileException, ClassNotFoundException {
		ClassLoader parentClassLoader = Thread.currentThread()
				.getContextClassLoader();
		ClassLoader cl = new MyClassLoader2(parentClassLoader);
		cl.loadClass("prova.MyClass");

		MyClass k = new MyClass();

		assertEquals(0, k.getCurrentValue());

		k.increment();

		assertEquals(3, k.getCurrentValue());

	}

	@Test
	public void shouldSuccessivelyDecrementWhileIncrementing()
			throws ClassNotFoundException {
		final SynchUnit synchUnit = SynchUnit.getInstance();

		ClassLoader parentClassLoader = Thread.currentThread()
				.getContextClassLoader();
		ClassLoader cl = new MyClassLoader3(parentClassLoader);
		cl.loadClass("prova.MyClass");

		final MyClass myClass = new MyClass();

		MonitoredThread incrementingThread = new MonitoredThread(
				"Incrementing Thread") {
			public void executeCode() {
				myClass.increment();
			}
		};
		synchUnit.monitor(incrementingThread);

		MonitoredThread decrementingThread = new MonitoredThread(
				"Decrementing Thread") {
			public void executeCode() {
				myClass.decrement();
			}
		};
		decrementingThread.skipAllBreakpoints();
		synchUnit.monitor(decrementingThread);

		incrementingThread.resumeExecution();
		assertEquals(State.WAITING, incrementingThread.getState());

		decrementingThread.resumeExecution();
		assertEquals(State.BLOCKED, decrementingThread.getState());

		assertEquals(0, myClass.getCurrentValue());

		incrementingThread.resumeExecution();
		assertEquals(State.WAITING, incrementingThread.getState());

		assertEquals(1, myClass.getCurrentValue());

		incrementingThread.resumeExecution();
		assertEquals(State.TERMINATED, incrementingThread.getState());

		assertEquals(State.TERMINATED, decrementingThread.getState());

		assertEquals(0, myClass.getCurrentValue());
	}

	public static class MyClassLoader1 extends ClassLoader {

		public MyClassLoader1(ClassLoader parent) {
			super(parent);
		}

		public Class loadClass(String name) throws ClassNotFoundException {
			if (!"prova.MyClass".equals(name))
				return super.loadClass(name);

			try {
				CtClass ctClass = getDefault().get("prova.MyClass");

				CtMethod method = ctClass.getDeclaredMethod("increment");

				StringBuffer content = new StringBuffer();
				content.append("{\n i--;\n } ");

				method.insertBefore(content.toString());

				return ctClass.toClass();

			} catch (Exception e) {
				throw new RuntimeException("Cannot load class " + name, e);
			}
		}

	}

	public static class MyClassLoader2 extends ClassLoader {

		public MyClassLoader2(ClassLoader parent) {
			super(parent);
		}

		public Class loadClass(String name) throws ClassNotFoundException {
			if (!"prova.MyClass".equals(name))
				return super.loadClass(name);

			try {
				CtClass ctClass = getDefault().get("prova.MyClass");

				CtMethod method = ctClass.getDeclaredMethod("increment");

				StringBuffer content = new StringBuffer();
				content.append("{\n i++; i++;\n } ");

				method.insertBefore(content.toString());

				return ctClass.toClass();

			} catch (Exception e) {
				throw new RuntimeException("Cannot load class " + name, e);
			}
		}

	}

	public static class MyClassLoader3 extends ClassLoader {

		public MyClassLoader3(ClassLoader parent) {
			super(parent);
		}

		public Class loadClass(String name) throws ClassNotFoundException {
			if (!"prova.MyClass".equals(name))
				return super.loadClass(name);

			try {
				CtClass ctClass = getDefault().get("prova.MyClass");

				CtMethod method = ctClass.getDeclaredMethod("increment");

				StringBuffer content = new StringBuffer();
				content.append("{\n com.google.code.synchunit.SynchUnit.getInstance().suspendHere(); \n } ");

				method.insertBefore(content.toString());
				method.insertAfter(content.toString());

				return ctClass.toClass();

			} catch (Exception e) {
				throw new RuntimeException("Cannot load class " + name, e);
			}
		}

	}

}
